package com.github.cyf.common.excel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.util.NumberUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.github.cyf.common.excel.converter.LocalDateStringConverter;
import com.github.cyf.common.excel.converter.LocalDateTimeStringConverter;
import com.github.cyf.common.excel.converter.LocalTimeStringConverter;
import com.github.cyf.common.excel.entity.CellMergeStrategy;
import com.github.cyf.common.excel.entity.ExcelPageResult;
import com.github.cyf.common.excel.listener.DefaultDataListener;
import com.github.cyf.common.util.NumberUtil;
import org.apache.commons.math3.util.MathUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.http.HttpHeaders;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * <p>
 * Excel 批量导入导出
 * </p>
 *
 * 官方文档：https://easyexcel.opensource.alibaba.com/docs
 *
 */
public class ExcelUtil {

    // --------------------------------------------- 读取 --------------------------------------------------

    /**
     * 读取 excel 内容，使用 map 接收数据，key 为 列数，value 为数据
     * @param filePath 文件路径
     * @return
     */
    public static List<LinkedHashMap<Integer, String>> read(String filePath) {
        return EasyExcel.read(filePath).headRowNumber(0).sheet().doReadSync();
    }

    /**
     * 读取 excel 内容，使用 map 接收数据，key 为 列数，value 为数据
     * @param filePath 文件路径
     * @param sheetName sheet名称
     * @return
     */
    public static List<LinkedHashMap<Integer, String>> read(String filePath, String sheetName) {
        return EasyExcel.read(filePath).headRowNumber(0).sheet(sheetName).doReadSync();
    }

    /**
     * 读取 excel 内容，使用默认的监听器
     * @param filePath 文件路径
     * @param head 表头行数量，默认为 1
     * @return
     */
    public static <T> List<T> read(String filePath, Class<T> head) {
        return read(filePath, null, 1, head);
    }

    /**
     * 读取 excel 内容，使用默认的监听器
     *
     * @param filePath  文件路径
     * @param sheetName sheet名称
     * @param headRowNumber 表头行数量，默认为 1
     * @param head 数据对应的实体类
     * @param <T>
     * @return
     */
    public static <T> List<T> read(String filePath, String sheetName, int headRowNumber, Class<T> head) {
        try (FileInputStream is = new FileInputStream(filePath)) {
            return read(is, sheetName, headRowNumber, head);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 读取 excel 内容，使用默认的数据监听器
     * @param is 输入流
     * @param sheetName sheet名称，如果为 null，则读取第一个 sheet
     * @param headRowNumber 表头行数量，默认为 1，即数据从第 2 行开始
     * @param head 数据对应的实体类
     * @param <T>
     * @return
     */
    public static <T> List<T> read(InputStream is, String sheetName, int headRowNumber, Class<T> head) {
        DefaultDataListener<T> listener = new DefaultDataListener();
        EasyExcel.read(is, head, listener).headRowNumber(headRowNumber).sheet(sheetName).doRead();
        if (listener.hasError()) {
            throw new RuntimeException("文件数据有误！" + listener.getErrorList());
        }
        return listener.getList();
    }



    // ------------------------------------------------- 导出 -----------------------------------------------

    public static <T> void export(String path, List<T> dataList) {
        File file = new File(path);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        EasyExcel.write(path).sheet("sheet1").doWrite(dataList);
    }

    /**
     * 批量导出 Excel 文件
     * @param path 文件保存路径
     * @param dataList  数据列表
     * @param head 数据对应的实体类
     */
    public static <T> void export(String path, Class<T> head, List<T> dataList) {
        File file = new File(path);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        try (FileOutputStream os = new FileOutputStream(file)) {
            export(os, head, dataList);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 批量导出 Excel 文件
     * @param os 输出流
     * @param dataList  数据列表
     * @param head 数据对应的实体类
     */
    public static <T> void export(OutputStream os, Class<T> head, List<T> dataList) {
        EasyExcel.write(os, head)
                .registerConverter(LocalDateTimeStringConverter.INSTANCE)
                .registerConverter(LocalTimeStringConverter.INSTANCE)
                .registerConverter(LocalDateStringConverter.INSTANCE)
                .sheet("sheet1").doWrite(dataList);
    }


    // --------------------------------------- 下载 -----------------------------------------------------

    /**
     * 将指定的工作簿内容以文件形式下载到客户端
     * @param fileName 文件名
     * @param head    Excel 类
     * @param dataList 数据列表
     * @param <T>
     */
    public static <T> void download(String fileName, Class<T> head, List<T> dataList, HttpServletResponse response) throws IOException {
        response.setContentType("application/vnd.ms-excel");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename='" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
        export(response.getOutputStream(), head, dataList);
    }



    // -------------------------------------------- sheet ------------------------------------------------------

    /**
     * 获取 sheet
     * @param filePath 文件路径
     */
    public static List<ReadSheet> getAllSheet(String filePath) {
        try (ExcelReader excelReader = EasyExcel.read(filePath).build()) {
            return excelReader.excelExecutor().sheetList();
        }
    }

    /**
     * 获取 sheet
     * @param is 输入流
     */
    public static List<ReadSheet> getAllSheet(InputStream is) {
        try (ExcelReader excelReader = EasyExcel.read(is).build()) {
            return excelReader.excelExecutor().sheetList();
        }
    }



    // ---------------------------------------------- 单元格 ---------------------------------------------------

    /**
     * 在单元格区域内填充内容
     * @param sheet
     * @param cellRangeAddress 单元格区域
     * @param content 内容
     */
    public static void fillCellRange(Sheet sheet, CellRangeAddress cellRangeAddress, String content) {
        // 创建合并的单元格区域的第一行第一列
        Cell cell = getCell(sheet, cellRangeAddress.getFirstRow(), cellRangeAddress.getFirstColumn());

        // 设置内容样式居中
        CellStyle style = sheet.getWorkbook().createCellStyle();
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        cell.setCellStyle(style);

        // 填充单元格值
        cell.setCellValue(content);

        // 添加合并区域到 sheet
        sheet.addMergedRegion(cellRangeAddress);
    }

    /**
     * 根据索引获取单元格，没有则创建
     * @param sheet
     * @param rowIndex
     * @param cellIndex
     * @return
     */
    public static Cell getCell(Sheet sheet, int rowIndex, int cellIndex) {
        Row row = sheet.getRow(rowIndex);
        if (row == null) {
            row = sheet.createRow(rowIndex);
        }
        Cell cell = row.getCell(cellIndex);
        if (cell == null) {
            cell = row.createCell(cellIndex);
        }
        return cell;
    }


    // ---------------------------- 单元格合并 ---------------------------------------
    /**
     * 按照特定的单元格合并策略，将数据列表以指定类头的形式导出至 Excel 文件中。
     * @param filePath 文件路径
     * @param dataList 数据列表
     * @param head 数据对应的实体类
     * @param cellMergeStrategy 单元格合并策略
     * @param <T>
     */
    public static <T> void exportExcelWithCellMerge(String filePath, Class<T> head, List<T> dataList, CellMergeStrategy cellMergeStrategy) {
        EasyExcel.write(filePath, head).registerWriteHandler(cellMergeStrategy).sheet().doWrite(dataList);
    }


    // ----------------------------------- 大数据 -------------------------------------------
    /**
     * 导出大数据量的 Excel 文件
     * @param exportPath
     * @param head
     * @param dataList
     * @param batchSize
     * @param <T>
     */
    public static <T> void exportBigData(String exportPath, Class<T> head, List<T> dataList, int batchSize) {
        File file = new File(exportPath);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        int sheetCount = NumberUtil.divOfCeiling(dataList.size(), batchSize);
        try (ExcelWriter excelWriter = EasyExcel.write(exportPath).build()) {
            for (int i = 0; i < sheetCount; i++) {
                int toIndex = Math.min(i + batchSize, dataList.size());
                List<T> subList = dataList.subList(i * batchSize, toIndex);
                WriteSheet writeSheet = EasyExcel.writerSheet("Sheet" + (i + 1)).head(head).build();
                excelWriter.write(subList, writeSheet);
            }
            excelWriter.finish();
        }
    }

    /**
     * 计算导出配置
     * @param totalCount    需要导出到 excel 的数据量
     * @param sheetDataRows 每个 sheet 存放的数据量
     * @param writeDataRows 每次写入 sheet 的数据量
     * @return
     */
    public static ExcelPageResult calculateExportConfiguration(int totalCount, int sheetDataRows, int writeDataRows) {
        // 计算需要的 sheet 数量
        int sheetNum = NumberUtil.divOfCeiling(totalCount, sheetDataRows);

        int oneSheetWriterCount, lastSheetWriteCount;
        // 计算每一个 Sheet 需要写入的次数
        oneSheetWriterCount = totalCount < sheetDataRows ?
                NumberUtil.divOfCeiling(totalCount, writeDataRows) : NumberUtil.divOfCeiling(sheetDataRows, writeDataRows);

        // 计算最后一个 sheet 需要写入的次数
        lastSheetWriteCount = totalCount % sheetDataRows == 0 ?
                oneSheetWriterCount : NumberUtil.divOfCeiling(totalCount % sheetDataRows, writeDataRows);

        return new ExcelPageResult(sheetNum, oneSheetWriterCount, lastSheetWriteCount);
    }
}
